package nl.Viewer.ViewManager.Views;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLJPanel;
import javax.media.opengl.glu.GLU;

import nl.Viewer.SerializedObjects.Parameter;
import nl.Viewer.ViewManager.Controllers.GenericController;
import nl.Viewer.ViewManager.Models.DataStores.ImageDataObject;
import nl.Viewer.ViewManager.Objects.RendererHelperFunctions;

/**
 * An implementation of a view which shows a reconstructed plane from a volume. The view allows manipulation of the
 * cutting plane.
 * 
 * The SHIFT-key will start the cut plane interaction mode. Left mousebutton will either pan the cut image, or rotate
 * the cutplane, the right mousebutton will either zoom the cut image, or push/pull the cutplane towards the origin.
 * 
 * 
 * 
 * 
 * @author Jeroen Verschuur
 * 
 */

public class JOGLImageDataObjectAsPlaneView extends GenericView implements GLEventListener, MouseListener,
      MouseMotionListener, MouseWheelListener, KeyListener {
   private static final long serialVersionUID            = -2958949544243327703L;

   /********************************************************************************************************************
    * 
    * Interaction properties
    * 
    */

   protected Point           lastLocation                = null;
   // set to true if dragging the mouse pointer out of the panel
   private boolean           dragOutOfScreen             = true;
   /* viewing parameters */
   protected boolean         interactionAllowed          = false;

   protected Parameter       CutPlaneNormal;
   protected double          cutPlaneNormalValue[]       = new double[] { 0.0, 0.0, 1.0 };

   protected Parameter       CutPlaneOrigin;
   protected double          cutPlaneOriginValue[]       = new double[] { 0.0, 0.0, 0.0 };

   protected Parameter       SourceObjectBounds;
   protected double          sourceObjectBoundsValue[]   = new double[] { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

   protected Parameter       ViewSize;
   protected int             viewSizeValue[]             = new int[] { 0, 0 };

   protected Parameter       AnnotationFocalPoint;
   protected double          annotationFocalPointValue[] = new double[] { 0, 0 };

   protected Parameter       ZoomParameter               = null;
   protected double          zoomParameterValue          = 1;

   protected Parameter       BaseImageOrOverlay          = null;
   protected boolean         baseImageOrOverlayValue     = true;

   private boolean           parametersRetrieved         = false;

   protected double          rotation                    = 0.0;

   /********************************************************************************************************************
    * 
    */

   protected int             panelWidth                  = 0, panelHeight = 0;
   protected Double          aspect                      = 1.0;
   protected GLU             glu                         = new GLU();
   protected ImageDataObject baseImageBuffer             = null;
   protected double[]        baseImageLowerLeft          = new double[] { 0, 0 };
   protected double          baseImageScale              = 1.0;
   protected ImageDataObject overlayImageBuffer          = null;
   protected double[]        overlayImageLowerLeft       = new double[] { 0, 0 };

   protected Double          minScale                    = 1.0;
   protected Double          scale                       = 1.0;
   protected Double          screenCenterX               = 0.0, screenCenterY = 0.0;

   protected boolean[]       waitingRenders              = new boolean[] { true, true };
   // two booleans to signal that either the plane or the image is being interacted on
   protected boolean         planeInteracting            = false;
   protected boolean         imageInteracting            = false;
   // two booleans to specify changes to either the image placement/zoom or the plane normal/distance
   protected boolean         imageInteractionAltered     = false;
   protected boolean         planeInteractionAltered     = false;

   public JOGLImageDataObjectAsPlaneView( GenericController parent ) {
      super( parent );
      this.renderPanel = new GLJPanel( getCapabilities() );

      ( (GLJPanel)renderPanel ).addGLEventListener( this );
      renderPanel.addMouseListener( this );
      renderPanel.addMouseMotionListener( this );
      renderPanel.addMouseWheelListener( this );
      parent.addKeyListener( this );

      parent.getDataModel().addParameterModelChangeListener( this );
      parent.getDataModel().addDataStoreModelChangeListener( this );

   }

   public static GLCapabilities getCapabilities() {
      GLCapabilities c = new GLCapabilities();
      c.setDoubleBuffered( true );
      return c;
   }

   /**
    * Called by drawable to indicate mode or device has changed
    */
   public void displayChanged( GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged ) {
   }

   /**
    * Called after OpenGL is init'ed
    */
   public void init( GLAutoDrawable drawable ) {
      GL gl = drawable.getGL();
      // set erase color
      gl.glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); // white
      // set drawing color and point size
      gl.glColor3f( 0.0f, 0.0f, 0.0f );
      gl.glPointSize( 1.0f ); // a 'dot' is 4 by 4 pixel

      // enable alpha mask (import from gif sets alpha bits)
      gl.glBlendFunc( GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA );
      gl.glEnable( GL.GL_BLEND );
   }

   private int oldX      = -1;
   private int oldY      = -1;
   private int oldWidth  = -1;
   private int oldHeight = -1;

   /**
    * Called to indicate the drawing surface has been moved and/or resized
    */
   public void reshape( GLAutoDrawable drawable, int x, int y, int width, int height ) {
      GL gl = drawable.getGL();
      gl.glViewport( 0, 0, width, height );
      gl.glMatrixMode( GL.GL_PROJECTION );
      gl.glLoadIdentity();
      panelWidth = width;
      panelHeight = height;
      aspect = (double)panelWidth / (double)panelHeight;
      glu.gluOrtho2D( 0.0, width, 0.0, height );

      resetScale();

      if( interactionAllowed && ( x != oldX || y != oldY || width != oldWidth || height != oldHeight ) ) {
         requireNewOverlayRender(); // specify that we want a new base render (and overlay)
      }
      oldX = x;
      oldY = y;
      oldWidth = width;
      oldHeight = height;
   }

   private synchronized void requireNewBaseRender() {
      this.waitingRenders[0] = true;
      this.waitingRenders[1] = true;
      requestNewRender();
   }

   private synchronized void requireNewOverlayRender() {
      this.waitingRenders[1] = true;
      requestNewRender();
   }

   private synchronized void requestNewRender() {
      if( waitingRenders[0] || waitingRenders[1] )
         parent.requestNewRender();

   }

   public void display() {
      ( (GLJPanel)renderPanel ).display();
   }

   /**
    * Called by drawable to initiate drawing
    */
   public void display( GLAutoDrawable drawable ) {
      GL gl = drawable.getGL();
      gl.glClear( GL.GL_COLOR_BUFFER_BIT );

      // 2D
      gl.glMatrixMode( GL.GL_PROJECTION );
      gl.glLoadIdentity();
      glu.gluOrtho2D( 0.0, panelWidth, 0.0, panelHeight );
      gl.glDisable( GL.GL_DEPTH_TEST );

      gl.glMatrixMode( GL.GL_MODELVIEW );
      gl.glLoadIdentity();

      drawImages( gl );

      if( SourceObjectBounds != null && planeInteracting && interactionAllowed ) {
         double viewAngle = 30;
         Double[] eye = new Double[ 3 ];
         Double[] center = new Double[ 3 ];
         Double[] viewUp = new Double[] { 0.0, 1.0, 0.0 };

         // calculate new camera parameters to center the object
         RendererHelperFunctions.resetCamera( viewAngle, sourceObjectBoundsValue, eye, center );

         // 3D if interacting
         gl.glMatrixMode( GL.GL_PROJECTION );
         gl.glLoadIdentity();
         glu.gluPerspective( viewAngle / 1, aspect, 0.0001/* clippingRange[0] */, 10000.0 /* clippingRange[1] */);

         gl.glMatrixMode( GL.GL_MODELVIEW );
         gl.glLoadIdentity();

         glu.gluLookAt( eye[0], eye[1], eye[2], center[0], center[1], center[2], viewUp[0], viewUp[1], viewUp[2] );

         drawPlane( gl );
         // cutPlaneNormalValue[0] = Math.sin( Math.PI/4 );
         // cutPlaneNormalValue[2] = Math.cos( Math.PI/4 );
         // System.out.println( "cutplane =" + rotation + "/ " + cutPlaneNormalValue[0] + "/" + cutPlaneNormalValue[1]
         // );
         // rotation += 0.1;
         // cutPlaneAltered = true;
      }

      gl.glFlush();
      ( (GLJPanel)renderPanel ).swapBuffers();
   }

   public synchronized void drawPlane( GL gl ) {
      RendererHelperFunctions.drawBox( gl, sourceObjectBoundsValue[0], sourceObjectBoundsValue[1],
            sourceObjectBoundsValue[2], sourceObjectBoundsValue[3], sourceObjectBoundsValue[4],
            sourceObjectBoundsValue[5] );

      // calculate (1-6) intersections with bounding box and draw them
      ArrayList<double[]> sortedVertices = RendererHelperFunctions.planeToBoundingBoxIntersection( cutPlaneNormalValue,
            cutPlaneOriginValue, sourceObjectBoundsValue );

      gl.glColor4f( 1, 1, 1, 1 );
      gl.glLineWidth( 1 );
      gl.glPolygonMode( GL.GL_FRONT_AND_BACK, GL.GL_FILL );
      gl.glEnable( GL.GL_POLYGON_SMOOTH );

      if( sortedVertices != null ) {
         gl.glBegin( GL.GL_POLYGON );

         for( int i = 0; i < sortedVertices.size(); i++ ) {
            double[] v = sortedVertices.get( i );
            gl.glVertex3d( v[0], v[1], v[2] );

            if( i < sortedVertices.size() - 1 ) {
               v = sortedVertices.get( i + 1 );
            } else {
               v = sortedVertices.get( 0 );
            }
            gl.glVertex3d( v[0], v[1], v[2] );
         }

         gl.glEnd();
      }

   }

   private void drawImage( GL gl, ImageDataObject ido, double scale, double[] lowerLeft ) {
      // System.out.println( "drawImage " + ido );
      if( ido == null || ido.data == null )
         return;

      // System.out.println( "drawImage " + ido + "<=== !null => " + ido.width + "," + ido.height );
      /*
       * calculate the offset needed to get the requested imageCenter in the center of the frame
       */
      Dimension frameDimension = ( (GLJPanel)renderPanel ).getSize();

      double xOffset = -screenCenterX + ( lowerLeft[0] * scale ) + ( frameDimension.width / 2 );
      double yOffset = -screenCenterY + ( lowerLeft[1] * scale ) + ( frameDimension.height / 2 );

      gl.glPixelZoom( (float)scale, (float)scale );
      gl.glRasterPos2d( 0, 0 );
      /*
       * NOTES To set a valid raster position outside the viewport, first set a valid raster position inside the
       * viewport, then call glBitmap with NULL as the bitmap parameter and with xmove and ymove set to the offsets of
       * the new raster position. This technique is useful when panning an image around the viewport.
       */
      gl.glBitmap( 0, 0, 0, 0, (float)xOffset, (float)yOffset, null );

      if( planeInteracting ) {
         gl.glPixelTransferf( GL.GL_RED_SCALE, 0.3f );
         gl.glPixelTransferf( GL.GL_GREEN_SCALE, 0.3f );
         gl.glPixelTransferf( GL.GL_BLUE_SCALE, 0.3f );
      }

      // we should always synchronize on the buffer to prevent access by for example the DataStore or other views
      synchronized( ido ) {
         ido.data.rewind();
         gl.glDrawPixels( ido.width, ido.height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, ido.data );
      }

      if( planeInteracting ) {
         gl.glPixelTransferf( GL.GL_RED_SCALE, 1.0f );
         gl.glPixelTransferf( GL.GL_GREEN_SCALE, 1.0f );
         gl.glPixelTransferf( GL.GL_BLUE_SCALE, 1.0f );
      }
      // System.out.println( "Ido: " + ido + ", " + xOffset + ", " + yOffset + ", " + scale );
   }

   public synchronized void drawImages( GL gl ) {
      drawImage( gl, baseImageBuffer, this.zoomParameterValue, baseImageLowerLeft );
      drawImage( gl, overlayImageBuffer, scale, overlayImageLowerLeft );
   }

   /*
    * Parameter methods
    * 
    */

   public void parameterValueChanged( Parameter p ) {
      synchronized( this ) {
         synchronizeParameterValues();
      }
   }

   public void dataStoreChanged( boolean newDataStore ) {
      synchronized( this ) {
         if( newDataStore ) {
            activateDataStore(); // process a new render (updates to the datastore are not relevant, only new image data
            // objects are)
         } else {
            // ignore, do nothing for now
         }
      }
   }

   public synchronized void activateDataStore() {
      Object currentData = parent.getDataStore().getCurrentData();
      if( currentData == null ) {
         System.out.println( "No datastore available yet" );
      } else {
         if( currentData instanceof ImageDataObject == false ) {
            System.out.println( "MUST have ImageDataObject (we got " + currentData.getClass() + ")" );
            currentData = null;
         }
      }
      // get new viewing parameters from the server
      synchronizeParameterValues();

      if( waitingRenders[0] == true ) {
         if( currentData != null )
            setBaseBuffer( (ImageDataObject)currentData );

         waitingRenders[0] = false;
         requestNewRender(); // check if new renders are needed
      } else {
         if( currentData != null )
            setOverlayBuffer( (ImageDataObject)currentData );

         waitingRenders[1] = false;
         requestNewRender(); // check if new renders are needed
      }
   }

   private void setBaseBuffer( ImageDataObject ido ) {
      overlayImageBuffer = null;
      baseImageBuffer = ido;
      baseImageLowerLeft[0] = -( ido.width / 2 );
      baseImageLowerLeft[1] = -( ido.height / 2 );

      // reset the screen center and the scale when a new base image is rendered
      screenCenterX = 0.0;
      screenCenterY = 0.0;
      resetScale();

      // also: allow interaction only after rendering
      interactionAllowed = true;
   }

   private void setOverlayBuffer( ImageDataObject ido ) {
      // don't reset the screencenter for the overlay, like in setBaseBuffer(..)
      overlayImageBuffer = ido;

      // calculate the lower left corner in view space that the overlay image should get
      // don't forget to account for the zoom factor that is in use!
      overlayImageLowerLeft[0] = ( annotationFocalPointValue[0] * zoomParameterValue ) - ( ido.width / 2 );
      overlayImageLowerLeft[1] = ( annotationFocalPointValue[1] * zoomParameterValue ) - ( ido.height / 2 );
   }

   private void getAllParameters() {
      if( parametersRetrieved )
         return;

      ConcurrentHashMap<String, Parameter> parameters = parent.getParameters();

      CutPlaneNormal = parameters.get( "CutPlaneNormal" );
      if( CutPlaneNormal == null )
         System.out.println( this.getClass() + " expected parameter CutPlaneNormal to exist on the server" );

      CutPlaneOrigin = parameters.get( "CutPlaneOrigin" );
      if( CutPlaneOrigin == null )
         System.out.println( this.getClass() + " expected parameter CutPlaneOrigin to exist on the server" );

      SourceObjectBounds = parameters.get( "SourceObjectBounds" );
      if( SourceObjectBounds == null )
         System.out.println( this.getClass() + " expected parameter SourceObjectBounds to exist on the server" );

      ZoomParameter = parameters.get( "Zoom" );
      if( ZoomParameter == null )
         System.out.println( this.getClass() + " expected parameter Zoom to exist on the server" );

      ViewSize = parameters.get( "ViewSize" );
      if( ViewSize == null )
         System.out.println( this.getClass() + " expected parameter ViewSize to exist on the server" );

      AnnotationFocalPoint = parameters.get( "AnnotationFocalPoint" );
      if( AnnotationFocalPoint == null )
         System.out.println( this.getClass() + " expected parameter AnnotationFocalPoint to exist on the server" );

      BaseImageOrOverlay = parameters.get( "BaseImageOrOverlay" );
      if( BaseImageOrOverlay == null )
         System.out.println( this.getClass() + " expected parameter BaseImageOrOverlay to exist on the server" );

      getRequiredParameterDefaults();

      parametersRetrieved = true;

   }

   /**
    * In this method all parameters for which we want the defaults from the database are specified
    * 
    */
   private void getRequiredParameterDefaults() {
      // we could get the parameter defaults if we would like to (for now, we just set the values ourselves)
      /*
       * if( CutPlaneNormal != null ) cutPlaneNormalValue = objectToDoubleArray( CutPlaneNormal.getParameterValue() );
       * 
       * if( CutPlaneOrigin != null ) cutPlaneOriginValue = objectToDoubleArray( CutPlaneOrigin.getParameterValue() );
       */
   }

   /**
    * This method fetches all parameter values from parameters that may be influenced from outside (the server, other
    * views, etc)
    */
   private void synchronizeParameterValues() {
      if( SourceObjectBounds != null )
         sourceObjectBoundsValue = GenericView.objectToDoubleArray( SourceObjectBounds.getParameterValue() );

      if( CutPlaneNormal != null )
         cutPlaneNormalValue = GenericView.objectToDoubleArray( CutPlaneNormal.getParameterValue() );

      if( CutPlaneOrigin != null )
         cutPlaneOriginValue = GenericView.objectToDoubleArray( CutPlaneOrigin.getParameterValue() );

      super.synchronizeViews();
   }

   private void setVisibilityProperties() {
      // calculate the visible rectangle in image space
      Dimension panelSize = this.getRenderPanel().getSize();

      if( ZoomParameter != null ) {
         ZoomParameter.setParameterValue( zoomParameterValue );
         ZoomParameter.setModified( true );
      }

      if( ViewSize != null ) {
         ViewSize.setParameterValue( new int[] { panelSize.width, panelSize.height } );
         ViewSize.setModified( true );
      }

      if( AnnotationFocalPoint != null ) {
         annotationFocalPointValue[0] = screenCenterX / zoomParameterValue;
         annotationFocalPointValue[1] = screenCenterY / zoomParameterValue;
         AnnotationFocalPoint.setParameterValue( annotationFocalPointValue );
         AnnotationFocalPoint.setModified( true );
      }
   }

   /*
    * This method sets all parameters before each render
    */
   public void setPreRenderParameters() {
      // this should actually be done in the constructor, however, the list of parameters is not yet retrieved when this
      // class is constructed

      boolean isFirstRender = false;
      if( !parametersRetrieved ) {
         isFirstRender = true;
      }

      // getAllParameters will set parametersRetrieved to true in case this was the first render
      getAllParameters();

      // calculate the visible rectangle
      setVisibilityProperties();

      // only set a new normal/origin value when this is NOT the first render
      // (so: have the python script decide on an initial value)
      if( !isFirstRender ) {
         // we need a base render
         System.out.println( "Setting cutplane parameters: " + cutPlaneNormalValue[0] + "/" + cutPlaneNormalValue[2] );

         if( CutPlaneNormal != null ) {
            CutPlaneNormal.setParameterValue( cutPlaneNormalValue );
            CutPlaneNormal.setModified( true );
         }

         if( CutPlaneOrigin != null ) {
            CutPlaneOrigin.setParameterValue( cutPlaneOriginValue );
            CutPlaneOrigin.setModified( true );
         }
      }

      if( this.waitingRenders[0] == true ) {
         BaseImageOrOverlay.setParameterValue( true );
      } else {
         BaseImageOrOverlay.setParameterValue( false );
      }

      // we have set the image we want to have (either the base image, or an annotation overlay)
      BaseImageOrOverlay.setModified( true );
   }

   public void resetScale() {
      scale = 1.0; // we should never scale up automatically

      // if( baseImageBuffer != null ) {
      // Dimension curSize = ( (GLJPanel)renderPanel ).getSize();
      // // determine a suitable scale factor (never scale up,
      // // use smallest scale factor for x- or y-scaling so the image will
      // // always fit)
      // double xscale = (double)curSize.width;
      // xscale /= (double)( ( ( baseImageBuffer.width / 2 ) - screenCenterX ) + baseImageBuffer.width );
      // double yscale = (double)curSize.height;
      // yscale /= (double)( ( ( baseImageBuffer.height / 2 ) - screenCenterY ) + baseImageBuffer.height );
      //
      // if( xscale < scale ) {
      // scale = xscale;
      // }
      // if( yscale < scale ) {
      // scale = yscale;
      // }
      // }
      System.out.println( "Image Panel scale: " + scale );

      minScale = scale;
      // zoomParameterValue = scale;
   }

   public void moveUp( double up ) {
      if( baseImageBuffer == null )
         return;

      double tempCenter = screenCenterY - ( up / scale );

      if( tempCenter <= -( baseImageBuffer.height / 2.0 ) ) {
         screenCenterY = -( baseImageBuffer.height / 2.0 ) + 1.0;
      } else if( tempCenter >= ( baseImageBuffer.height + ( baseImageBuffer.height / 2.0 ) ) ) {
         screenCenterY = baseImageBuffer.height + ( baseImageBuffer.height / 2.0 ) - 1.0;
      } else {
         screenCenterY = tempCenter;
      }
      imageInteractionAltered = true;
   }

   public void moveRight( double right ) {
      if( baseImageBuffer == null )
         return;

      double tempCenter = screenCenterX - ( right / scale );
      if( tempCenter <= -( baseImageBuffer.width / 2.0 ) ) {
         screenCenterX = -( baseImageBuffer.width / 2.0 ) + 1.0;
      } else if( tempCenter >= ( baseImageBuffer.width + ( baseImageBuffer.width / 2.0 ) ) ) {
         screenCenterX = ( baseImageBuffer.width + ( baseImageBuffer.width / 2.0 ) ) - 1.0;
      } else {
         screenCenterX = tempCenter;
      }
      imageInteractionAltered = true;
   }

   public void scale( double factor ) {
      // adjust the pre-render scale using a factor, never allow the scale to go below the 'minScale' parameter
      double tempScale = scale * factor;
      // if( tempScale < minScale ) {
      // this.scale = minScale;
      // } else {
      this.scale = tempScale;
      // }

      // also adjust the global scale parameter
      // we could calculate the allowed scale factor from the decision above, but that's probably not faster
      double tempZoomParameterValue = zoomParameterValue * factor;
      // if( zoomParameterValue < minScale ) {
      // zoomParameterValue = minScale;
      // } else {
      zoomParameterValue = tempZoomParameterValue;
      // }

      imageInteractionAltered = true;
   }

   public void pushCutPlane( double amount ) {
      // make sure we have a normalized normal (may be removed in the future when we keep normals normalized when
      // altering them)
      RendererHelperFunctions.normalize( cutPlaneNormalValue );

      cutPlaneOriginValue[0] -= ( amount * cutPlaneNormalValue[0] );
      cutPlaneOriginValue[1] -= ( amount * cutPlaneNormalValue[1] );
      cutPlaneOriginValue[2] -= ( amount * cutPlaneNormalValue[2] );

      planeInteractionAltered = true;
   }

   /*
    * Interaction methods
    * 
    */

   public void mouseReleased( MouseEvent event ) {
      // reset last mouse location
      lastLocation = null;

      if( imageInteracting ) {
         imageInteracting = false;
         if( imageInteractionAltered ) {
            imageInteractionAltered = false;
            requireNewOverlayRender();
         }
      }
   }

   public void mousePressed( MouseEvent event ) {
      // save last location on screen
      // interacting = true;
      lastLocation = event.getLocationOnScreen();

      if( !planeInteracting ) {
         imageInteracting = true;
      }

   }

   public void mouseDragged( MouseEvent event ) {
      if( !dragOutOfScreen && !( (GLJPanel)renderPanel ).contains( event.getPoint() ) ) {
         return;
      }

      if( lastLocation == null )
         return; // this should/can not happen!

      Point p = event.getLocationOnScreen();

      int xDist = ( p.x - lastLocation.x );
      int yDist = ( p.y - lastLocation.y );

      // check which mouse button was used
      if( ( event.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK ) == MouseEvent.BUTTON1_DOWN_MASK ) {
         if( planeInteracting ) { // this implicitly means SHIFT-key is also used
            rotatePlane( xDist, yDist );
            planeInteractionAltered = true;
            rotation += 0.1;
         } else {
            this.moveRight( xDist );
            this.moveUp( -yDist );
         }
      }

      if( ( event.getModifiersEx() & MouseEvent.BUTTON3_DOWN_MASK ) == MouseEvent.BUTTON3_DOWN_MASK ) {
         double relative = ( (double)yDist / (double)( (GLJPanel)renderPanel ).getHeight() );

         if( planeInteracting ) { // this means SHIFT-key is also used
            pushCutPlane( yDist );
         } else {
            scale( 1 - relative );
         }
      }

      lastLocation = event.getLocationOnScreen();

      display();
   }

   /**
    * Method to rotate the cutting plane when the mouse has been dragged.
    * 
    * @param xDist
    * @param yDist
    */
   private void rotatePlane( int xDist, int yDist ) {
      double azimuth = RendererHelperFunctions.normalAzimuth( cutPlaneNormalValue );
      double elevation = RendererHelperFunctions.normalElevation( cutPlaneNormalValue );
//      System.out.println( "Old Azi/Elev: " + ( azimuth * RendererHelperFunctions.RAD2DEG ) + "/" + ( elevation * RendererHelperFunctions.RAD2DEG ) + ",dist: " + xDist + "/" + yDist );
//      System.out.println( "CPN = " + cutPlaneNormalValue[0] + "/" + cutPlaneNormalValue[1] + "/" + cutPlaneNormalValue[2] );

      azimuth += ( xDist / (double)this.panelWidth ) * 2 * Math.PI;
      
      
      elevation += ( yDist / (double)this.panelHeight ) * Math.PI;
      if( elevation > Math.PI / 2 ) {
         elevation -= Math.PI;
      } else if( elevation < -Math.PI / 2 ) {
         elevation += Math.PI;
      }
      // azimuth += 0.1;

      RendererHelperFunctions.polarToCartesian( elevation, azimuth, cutPlaneNormalValue );
//      System.out.println( "CPN = " + cutPlaneNormalValue[0] + "/" + cutPlaneNormalValue[1] + "/" + cutPlaneNormalValue[2] );
//
//      System.out.println( "New Azi/Elev should be: " + ( azimuth * RendererHelperFunctions.RAD2DEG ) + "/" + ( elevation * RendererHelperFunctions.RAD2DEG ) );
//      //
//      double newAzimuth = RendererHelperFunctions.normalAzimuth( cutPlaneNormalValue );
//      double newElevation = RendererHelperFunctions.normalElevation( cutPlaneNormalValue );
//      System.out.println( "New Azi/Elev has become: " + ( newAzimuth * RendererHelperFunctions.RAD2DEG ) + "/" + ( newElevation * RendererHelperFunctions.RAD2DEG ) );

   }

   public void mouseWheelMoved( MouseWheelEvent event ) {
      if( planeInteracting ) {
         pushCutPlane( -(double)event.getWheelRotation() );
      } else {
         scale( 1 - ( (double)event.getWheelRotation() * 0.1 ) );
         requireNewOverlayRender();
      }
      // update the display
      display();
   }

   public void keyPressed( KeyEvent e ) {
      if( e.getKeyCode() == KeyEvent.VK_SHIFT && !imageInteracting ) {

         e.consume();
         if( !planeInteracting ) {
            planeInteracting = true;
            display();
         }
      }

   }

   public void keyReleased( KeyEvent e ) {
      if( e.getKeyCode() == KeyEvent.VK_SHIFT && planeInteracting ) {
         e.consume();
         planeInteracting = false;
         display();

         if( planeInteractionAltered ) {
            planeInteractionAltered = false;

            requireNewBaseRender();
         }
      }
   }

   public void keyTyped( KeyEvent e ) {
   }

}
